package abc;

import com.alibaba.fastjson.JSONObject;
import org.bouncycastle.crypto.InvalidCipherTextException;
import org.bouncycastle.crypto.engines.SM2Engine;
import org.bouncycastle.crypto.params.ECDomainParameters;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.jcajce.spec.SM2ParameterSpec;
import org.bouncycastle.jce.interfaces.ECPrivateKey;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.jce.spec.ECParameterSpec;
import org.bouncycastle.util.encoders.Base64;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.spec.ECGenParameterSpec;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.LinkedHashMap;
import java.util.Objects;

/**
 * @author lideng
 */
public class SM2_BC_1_7_Util {

    static {
        Security.addProvider(new BouncyCastleProvider());

    }

    private static final String EC = "EC";
    private static final String SM2 = "SM2";
    private static final String SM3_WITH_SM2 = "SM3WithSM2";
    private static final String SM2P256V1 = "sm2p256v1";
    private static final String BC = BouncyCastleProvider.PROVIDER_NAME;

    /**
     * 验签
     *
     * @param str
     * @param signatureBase64
     * @param mylmk_ali_pubkey
     * @return
     * @throws NoSuchAlgorithmException
     * @throws NoSuchProviderException
     * @throws InvalidKeySpecException
     * @throws InvalidAlgorithmParameterException
     * @throws InvalidKeyException
     * @throws SignatureException
     */
    public static boolean isSignature(String str, String signatureBase64, String mylmk_ali_pubkey) throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeySpecException, InvalidAlgorithmParameterException, InvalidKeyException, SignatureException {
        byte[] sign = Base64.decode(signatureBase64);
        byte[] x509 = Base64.decode(mylmk_ali_pubkey);
        X509EncodedKeySpec spec = new X509EncodedKeySpec(x509);
        KeyFactory factory = KeyFactory.getInstance("EC", BouncyCastleProvider.PROVIDER_NAME);
        PublicKey publicKey = factory.generatePublic(spec);
        Signature signature = Signature.getInstance("SM3withSM2", BouncyCastleProvider.PROVIDER_NAME);
        signature.setParameter(new SM2ParameterSpec("1234567812345678".getBytes(StandardCharsets.UTF_8)));
        signature.initVerify(publicKey);
        byte[] bytes = str.getBytes(StandardCharsets.UTF_8);
        signature.update(bytes);
        boolean ret = signature.verify(sign);
        return ret;
    }

    /**
     * 加签
     *
     * @param key
     * @param msg
     * @return
     * @throws NoSuchAlgorithmException
     * @throws NoSuchProviderException
     * @throws InvalidKeyException
     * @throws SignatureException
     */
    public static byte[] signature(PrivateKey key, byte[] msg) throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeyException, SignatureException {
        Signature signature = Signature.getInstance(SM3_WITH_SM2, BC);
        signature.initSign(key);
        signature.update(msg);
        return signature.sign();
    }

    /**
     * sm2解密
     *
     * @param prvStr
     * @param decode
     * @return
     * @throws NoSuchAlgorithmException
     * @throws NoSuchProviderException
     * @throws InvalidKeySpecException
     * @throws InvalidCipherTextException
     * @throws InvalidCipherTextException
     */
    public static byte[] decrypt(String prvStr, byte[] decode) throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeySpecException, InvalidCipherTextException, InvalidCipherTextException {
        KeyFactory factory = KeyFactory.getInstance("EC", BouncyCastleProvider.PROVIDER_NAME);
        ECPrivateKey key = (ECPrivateKey) factory.generatePrivate(new PKCS8EncodedKeySpec(Base64.decode(prvStr)));
        ECParameterSpec spec = key.getParameters();
        ECDomainParameters domainParameters = new ECDomainParameters(spec.getCurve(), spec.getG(), spec.getN());
        ECPrivateKeyParameters sk = new ECPrivateKeyParameters(key.getD(), domainParameters);
        SM2Engine sm2Engine = new SM2Engine(SM2Engine.Mode.C1C3C2);
        sm2Engine.init(false, sk);
        byte[] resultBody = sm2Engine.processBlock(decode, 0, decode.length);
        return resultBody;
    }

    /**
     * sm2加密
     *
     * @param key
     * @param plainText
     * @return
     * @throws NoSuchPaddingException
     * @throws NoSuchAlgorithmException
     * @throws NoSuchProviderException
     * @throws IllegalBlockSizeException
     * @throws BadPaddingException
     * @throws InvalidKeyException
     */
    public static byte[] encrypt(PublicKey key, byte[] plainText) throws NoSuchPaddingException, NoSuchAlgorithmException, NoSuchProviderException, IllegalBlockSizeException, BadPaddingException, InvalidKeyException {
        Cipher cipher = Cipher.getInstance(SM2, BC);
        cipher.init(Cipher.ENCRYPT_MODE, key);
        byte[] cipherText = cipher.doFinal(plainText);
        return C1C2C3Utils.changeC123ToC132(cipherText);
    }

    static class C1C2C3Utils {

        /**
         * C1长度
         */
        private static final int C1_LEN = 1 + 64;
        /**
         * C3长度
         */
        private static final int C3_LEN = 32;

        /**
         * 私有构造
         */
        private C1C2C3Utils() {
        }

        /**
         * 把C1C2C3的顺序更换成C1C3C2
         *
         * @param input 输入
         * @return 输出
         */
        public static byte[] changeC123ToC132(byte[] input) {
            checkLen(input);
            int c2Len = input.length - C1_LEN - C3_LEN;
            ByteBuffer buf = ByteBuffer.wrap(input);
            byte[] c1 = new byte[C1_LEN];
            buf.get(c1);
            byte[] c2 = new byte[c2Len];
            buf.get(c2);
            byte[] c3 = new byte[C3_LEN];
            buf.get(c3);
            buf.clear();
            buf.put(c1).put(c3).put(c2);
            return buf.array();
        }

        /**
         * 把C1C3C2的顺序更换成C1C2C3
         *
         * @param input 输入
         * @return 输出
         */
        public static byte[] changeC132ToC123(byte[] input) {
            checkLen(input);
            int c2Len = input.length - C1_LEN - C3_LEN;
            ByteBuffer buf = ByteBuffer.wrap(input);
            byte[] c1 = new byte[C1_LEN];
            buf.get(c1);
            byte[] c3 = new byte[C3_LEN];
            buf.get(c3);
            byte[] c2 = new byte[c2Len];
            buf.get(c2);
            buf.clear();
            buf.put(c1).put(c2).put(c3);
            return buf.array();
        }

        /**
         * 长度校验
         *
         * @param input 输入
         */
        private static void checkLen(byte[] input) {
            if (Objects.isNull(input)
                    || input.length <= C1_LEN + C3_LEN) {
                throw new IndexOutOfBoundsException("SM input C1C2C3 data length is short.");
            }
        }
    }

    static class KeyUtils {

        /**
         * 生成国密公私钥对
         * <p>
         * <code>String[0]</code> 私钥
         * <p>
         * <code>String[1]</code> 公钥
         *
         * @return
         * @throws Exception
         */
        public static String[] generateKey() throws Exception {
            KeyPairGenerator keyPairGenerator = null;
            SecureRandom secureRandom = new SecureRandom();
            ECGenParameterSpec sm2Spec = new ECGenParameterSpec("sm2p256v1");
            keyPairGenerator = KeyPairGenerator.getInstance("EC", new BouncyCastleProvider());
            keyPairGenerator.initialize(sm2Spec);
            keyPairGenerator.initialize(sm2Spec, secureRandom);
            KeyPair keyPair = keyPairGenerator.generateKeyPair();
            PrivateKey privateKey = keyPair.getPrivate();
            PublicKey publicKey = keyPair.getPublic();
            String[] result = {new String(Base64.encode(privateKey.getEncoded())), new String(Base64.encode(publicKey.getEncoded()))};
            return result;
        }

        /**
         * 将Base64转码的公钥串，转化为公钥对象
         *
         * @param publicKey
         * @return
         */
        public static PublicKey createPublicKey(String publicKey) {
            PublicKey publickey = null;
            try {
                X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(Base64.decode(publicKey));
                KeyFactory keyFactory = KeyFactory.getInstance("EC", new BouncyCastleProvider());
                publickey = keyFactory.generatePublic(publicKeySpec);
            } catch (Exception e) {
                e.printStackTrace();
            }
            return publickey;
        }

        /**
         * 将Base64转码的私钥串，转化为私钥对象
         *
         * @param privateKey
         * @return
         */
        public static PrivateKey createPrivateKey(String privateKey) {
            PrivateKey publickey = null;
            try {
                PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(Base64.decode(privateKey));
                KeyFactory keyFactory = KeyFactory.getInstance("EC", new BouncyCastleProvider());
                publickey = keyFactory.generatePrivate(pkcs8EncodedKeySpec);
            } catch (Exception e) {
                e.printStackTrace();
            }
            return publickey;
        }
    }

    public static void main(String[] args) {
        /*
        验签解密->加密加签过程

        蚂蚁私钥加签 	  蚂蚁公钥验签		                        行内私钥解密
        行内公钥加密	  sm3withsm2(Util.isSignature)		        sm2(Util.decrypt)
            |				 |	    		                       |
        request:	先验签(head & body)------------------------>再解密(body)
                                                                   |
                                                                   |
        response    再加签(head & body)<------------------------先加密(body)
                            |                                      |
                         sm3withsm2(Util.signature)               sm2(Util.encrypt)
        行内私钥加签       行内私钥加签                              蚂蚁公钥加密
        蚂蚁公钥加密

         */

        String mylmk_ali_pubkey = "MFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAEPRhUZtXzTNG5RSTT8nq6CEZ17TwOf2CK2RXjpDSdvRpkLywgFcfkXc9mlPs67KMVEKXTx65aWb6uxdmNPZ+LSw==";
        String mylmk_nb_pubkey = "MFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAEi225yYI6EnklD2Lyf7meYTBW0NwbxoAsz7e/PM4N2cWJ8v+IJrS5KmqlRwC1RwKmq/z2CEh/s5m+70ky6dKZIQ==";
        String mylmk_nb_privatey = "MIGTAgEAMBMGByqGSM49AgEGCCqBHM9VAYItBHkwdwIBAQQg+HkWstF6XKawry+1GZPu0hWDCh8HvSXkHlIaybCljKWgCgYIKoEcz1UBgi2hRANCAASLbbnJgjoSeSUPYvJ/uZ5hMFbQ3BvGgCzPt788zg3ZxYny/4gmtLkqaqVHALVHAqar/PYISH+zmb7vSTLp0pkh";

        PublicKey ali_pubkey = KeyUtils.createPublicKey(mylmk_ali_pubkey);
        PublicKey nb_pubkey = KeyUtils.createPublicKey(mylmk_nb_pubkey);
        PrivateKey nb_private = KeyUtils.createPrivateKey(mylmk_nb_privatey);

        //行内 明文请求
        String nb_request = "{\n" +
                "  \"applicationNo\": \"20230210002940000\",\n" +
                "  \"alipayLogonId\": \"251655198006776\"\n" +
                "}";

        try {
            System.out.println("----------加密----------开始----------加签----------");
            System.out.println();
            //sm2 蚂蚁公钥加密
            byte[] encryptSM2Body = encrypt(ali_pubkey, nb_request.getBytes(StandardCharsets.UTF_8));
            //Base64.encode 加密
            byte[] encode = Base64.encode(encryptSM2Body);
            String encBody = new String(encode, StandardCharsets.UTF_8);

            JSONObject nb_sortHead = new JSONObject(new LinkedHashMap<>());
            nb_sortHead.put("version", "1.0.0");
            nb_sortHead.put("appid", "TH001");
            nb_sortHead.put("method", "ant.current.unionCard.eventNotifyMessage");
            nb_sortHead.put("reqTime", LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
            nb_sortHead.put("reqMsgId", "1234567asdfasdf1123fda");
            nb_sortHead.put("bizPartnerId", "JXBX");
            nb_sortHead.put("reserve", "");

            //行内私钥钥 加签
            JSONObject nb_req = new JSONObject(new LinkedHashMap<>());
            nb_req.put("head", nb_sortHead);
            nb_req.put("body", encBody);
            byte[] signByte = signature(nb_private, nb_req.toString().getBytes(StandardCharsets.UTF_8));
            byte[] enc64Sign = Base64.encode(signByte);
            String nb_signature = new String(enc64Sign, StandardCharsets.UTF_8);
            JSONObject returnMsg = new JSONObject(new LinkedHashMap<>());
            returnMsg.put("request", nb_req);
            returnMsg.put("signature", nb_signature);
            System.out.println("sm3withsm2加签signature:" + nb_signature);
            System.out.println();
            System.out.println("sm2加密body:" + encBody);
            System.out.println();
            System.out.println("加密报文:" + returnMsg.toString());
            System.out.println();
            System.out.println("----------加密----------结束----------加签----------");
            System.out.println();
            System.out.println("----------验签----------开始----------解密----------");
            System.out.println();

            //蚂蚁 加密请求
            String head_body = "{\"head\":{\"version\":\"1.0.0\",\"appid\":\"NBBANK61\",\"method\":\"ant.current.unionCard.preApply\",\"reqTime\":\"2023-03-20T14:44:20\",\"reqMsgId\":\"20221130002940000288079198001234\",\"bizPartnerId\":\"SM2\",\"reserve\":\"\"},\"body\":\"BIyRNHXYzkuzx+Z9pKRPt/EKu5DY2kda3HLWKVGVExKFISq3az7FUVukbN/fJkwhlZSY1l6JDcig4h+eNCOsE77mEWWk+gqaWEmXs3Ef0lNTjztBiRHtp0uY5wRXEYQO2Xi9dLPeRz3Rl6tjOw9JkC/vw+2IpDxPJ27/wO9D5YylqxmuY5BNo2+9FJmNOy0nUaNUUA7SE7jWbfuRcB8vnvV4zximI4nMtn9kyjJ4EC0EXOgTatQNTuR5pNXvi9o/+KEhChN7WGb1MtjxZdGy3FP+Dpoer15j9S89bJogZ2yVSHP8DEP4D+YPSHUyxMLfpQzrLWVl1Zh6d6PxmI6HNnFTLaA+ss++q3rYVWVm5L65GxtZt3Ul7azFIXRPrvmPnz+6ADEM4/+B3vshxmSIHBI3CrALgNdlZRDF6pSMi8FIooK4KuyCd+MH0qOXpsBtORAuVCTMQsKr+kQWEEsxpRymyOdLN15GsLimzd7MGoXROcoHZplq6C+97ZnY+hE58AXQNT9VkQy3Al6Um9gnmJhfm9sPAPT9LT9cdIb+qKcCWnkhPnIRWAOS41jVv0vJR4CKBzT7jIHlkh/C4lgQ2WrV5PP8nu1GNgrAJV9eyXMZG3570g5TDXvfn7Pf0JarSLHWMNG+5CMShCq1LqXQjVtSCJyTuGEVqxkOjts/koESq6K1iNdbiToCUx4wIc2QM1y/pmTIDOCMEc6GUgCcWSnc0nRGC9C2uTzmGy5hT2i028faapnxmg4YrEOv0gk2V0BPWaYLYV27yKBosSPEN+j0hrQ0V96BTRNmckRD0IFu0MuY26zo4tHe3mTNXpM0lWUR72sF1hr6LR3C3ShthQE4nh0dJ2yzkOO0dHyUf2KP0OouanV8rSfWUO8=\"}";
            String signBase64 = "MEUCIQD7ySQd1TM016lK2eO6MsZdYvgzM6TPRwY3j9qf9yNDFgIgZu42cmzYfFdPIoyTHVtywkfsgCBQRSN1i3orYBbaGF4=";
            String body = "BIyRNHXYzkuzx+Z9pKRPt/EKu5DY2kda3HLWKVGVExKFISq3az7FUVukbN/fJkwhlZSY1l6JDcig4h+eNCOsE77mEWWk+gqaWEmXs3Ef0lNTjztBiRHtp0uY5wRXEYQO2Xi9dLPeRz3Rl6tjOw9JkC/vw+2IpDxPJ27/wO9D5YylqxmuY5BNo2+9FJmNOy0nUaNUUA7SE7jWbfuRcB8vnvV4zximI4nMtn9kyjJ4EC0EXOgTatQNTuR5pNXvi9o/+KEhChN7WGb1MtjxZdGy3FP+Dpoer15j9S89bJogZ2yVSHP8DEP4D+YPSHUyxMLfpQzrLWVl1Zh6d6PxmI6HNnFTLaA+ss++q3rYVWVm5L65GxtZt3Ul7azFIXRPrvmPnz+6ADEM4/+B3vshxmSIHBI3CrALgNdlZRDF6pSMi8FIooK4KuyCd+MH0qOXpsBtORAuVCTMQsKr+kQWEEsxpRymyOdLN15GsLimzd7MGoXROcoHZplq6C+97ZnY+hE58AXQNT9VkQy3Al6Um9gnmJhfm9sPAPT9LT9cdIb+qKcCWnkhPnIRWAOS41jVv0vJR4CKBzT7jIHlkh/C4lgQ2WrV5PP8nu1GNgrAJV9eyXMZG3570g5TDXvfn7Pf0JarSLHWMNG+5CMShCq1LqXQjVtSCJyTuGEVqxkOjts/koESq6K1iNdbiToCUx4wIc2QM1y/pmTIDOCMEc6GUgCcWSnc0nRGC9C2uTzmGy5hT2i028faapnxmg4YrEOv0gk2V0BPWaYLYV27yKBosSPEN+j0hrQ0V96BTRNmckRD0IFu0MuY26zo4tHe3mTNXpM0lWUR72sF1hr6LR3C3ShthQE4nh0dJ2yzkOO0dHyUf2KP0OouanV8rSfWUO8=";

            //蚂蚁公钥 验签
            boolean ret = isSignature(head_body, signBase64, mylmk_ali_pubkey);
            System.out.println("验签:" + ret);
            //Base64.decode 解密
            byte[] decode = Base64.decode(body);
            //sm2 行内私钥解密
            byte[] decrypt = decrypt(mylmk_nb_privatey, decode);
            String decBody = new String(decrypt, StandardCharsets.UTF_8);
            System.out.println("解密内容:" + decBody);
            System.out.println();
            System.out.println("----------验签----------结束----------解密----------");

        } catch (NoSuchPaddingException e) {
            e.printStackTrace();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (NoSuchProviderException e) {
            e.printStackTrace();
        } catch (IllegalBlockSizeException e) {
            e.printStackTrace();
        } catch (BadPaddingException e) {
            e.printStackTrace();
        } catch (InvalidKeyException e) {
            e.printStackTrace();
        } catch (SignatureException e) {
            e.printStackTrace();
        } catch (InvalidAlgorithmParameterException e) {
            e.printStackTrace();
        } catch (InvalidKeySpecException e) {
            e.printStackTrace();
        } catch (InvalidCipherTextException e) {
            e.printStackTrace();
        }


    }
}

